function mxEdgeHandler(state)
{
	if (state != null)
	{
		this.state = state;
		this.graph = this.state.view.graph;
		this.graph.addMouseListener(this);
		this.marker = this.createMarker();
		this.init();
	}
};
mxEdgeHandler.prototype.graph = null;
mxEdgeHandler.prototype.state = null;
mxEdgeHandler.prototype.marker = null;
mxEdgeHandler.prototype.error = null;
mxEdgeHandler.prototype.shape = null;
mxEdgeHandler.prototype.bends = null;
mxEdgeHandler.prototype.labelShape = null;
mxEdgeHandler.prototype.cloneEnabled = true;
mxEdgeHandler.prototype.init = function()
{
	this.points = [];

	this.abspoints = this.getSelectionPoints(this.state);
	this.shape = this.createSelectionShape(this.abspoints);
	this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG)
		? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
	this.shape.init(this.graph.getView().getOverlayPane());
	this.shape.node.style.cursor = 'pointer';
	var self = this;
	mxEvent.addListener(this.shape.node, 'dblclick', function(evt)
	{
		self.graph.dblClick(evt, self.state.cell);
		mxEvent.consume(evt);
	});
	mxEvent.addListener(this.shape.node, 'mousedown', function(evt)
	{
		self.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, self.state));
	});
	mxEvent.addListener(this.shape.node, 'mousemove', function(evt)
	{
		var cell = self.state.cell;

		if (self.index != null)
		{
			var gridEnabled = self.graph.isGridEnabledEvent(evt);
			var pt = mxUtils.convertPoint(self.graph.container, evt.clientX, evt.clientY, gridEnabled);
			cell = self.graph.getCellAt(pt.x, pt.y);

			if (self.graph.isSwimlane(cell) && self.graph.hitsSwimlaneContent(cell, pt.x, pt.y))
			{
				cell = null;
			}
		}
		self.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, self.graph.getView().getState(cell)));
	});
	mxEvent.addListener(this.shape.node, 'mouseup', function(evt)
	{
		self.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, self.state));
	});

	if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells || mxGraphHandler.prototype.maxCells <= 0)
	{
		this.bends = this.createBends();
	}
	this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
	this.labelShape = new mxRectangleShape(new mxRectangle(), mxConstants.LABEL_HANDLE_FILLCOLOR,
		mxConstants.HANDLE_STROKECOLOR);
	this.initBend(this.labelShape);
	this.labelShape.node.style.cursor = 'move';

	mxEvent.addListener(this.labelShape.node, 'dblclick', function(evt)
	{
		self.graph.dblClick(evt, self.state.cell);
		mxEvent.consume(evt);
	});
	mxEvent.addListener(this.labelShape.node, 'mousedown', function(evt)
	{
		self.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, self.state, mxEvent.LABEL_HANDLE));
	});
	this.redraw();
};
mxEdgeHandler.prototype.getSelectionPoints = function(state)
{
	return state.absolutePoints;
};
mxEdgeHandler.prototype.createSelectionShape = function(points)
{
	var shape = new mxPolyline(points, this.getSelectionColor());
	shape.strokewidth = this.getSelectionStrokeWidth();
	shape.isDashed = this.isSelectionDashed();
	return shape;
};
mxEdgeHandler.prototype.getSelectionColor = function()
{
	return mxConstants.EDGE_SELECTION_COLOR;
};
mxEdgeHandler.prototype.getSelectionStrokeWidth = function()
{
	return mxConstants.EDGE_SELECTION_STROKEWIDTH;
};
mxEdgeHandler.prototype.isSelectionDashed = function()
{
	return mxConstants.EDGE_SELECTION_DASHED;
};
mxEdgeHandler.prototype.createMarker = function()
{
	var marker = new mxCellMarker(this.graph);
	var self = this;

	marker.getCell = function(me)
	{
		var cell = mxCellMarker.prototype.getCell.apply(this, arguments);
		var model = self.graph.getModel();

		if (cell == self.state.cell || (cell != null && !self.graph.connectableEdges && model.isEdge(cell)))
		{
			cell = null;
		}
		return cell;
	};
	marker.isValidState = function(state)
	{
		var model = self.graph.getModel();
		var other = model.getTerminal(self.state.cell, !self.isSource);
		var source = (self.isSource) ? state.cell : other;
		var target = (self.isSource) ? other : state.cell;
		self.error = self.validateConnection(source, target);
		return self.error == null;
	};
	return marker;
};
mxEdgeHandler.prototype.validateConnection = function(source, target)
{
	return this.graph.getEdgeValidationError(this.state.cell, source, target);
};
mxEdgeHandler.prototype.createBends = function()
{
	var cell = this.state.cell;
	var bends = [];

	for (var i = 0; i < this.abspoints.length; i++)
	{
		if (this.isHandleVisible(i))
		{
			var source = i == 0;
			var target = i == this.abspoints.length - 1;
			var terminal = source || target;

			if (terminal || this.graph.isCellBendable(cell))
			{
				var bend = this.createHandleShape(i);
				this.initBend(bend);

				if (this.isHandleEnabled(i))
				{
					bend.node.style.cursor = 'all-scroll';
					this.installListeners(bend, bends.length);
				}
				bends.push(bend);

				if (!terminal)
				{
					this.points.push(new mxPoint(0, 0));
					bend.node.style.visibility = 'hidden';
				}
			}
		}
	}
	return bends;
};
mxEdgeHandler.prototype.isHandleEnabled = function(index)
{
	return true;
};
mxEdgeHandler.prototype.isHandleVisible = function(index)
{
	return !this.abspoints[index].isRouted;
};
mxEdgeHandler.prototype.createHandleShape = function(index)
{
	return new mxRectangleShape(new mxRectangle(), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
};
mxEdgeHandler.prototype.initBend = function(bend)
{
	if (this.graph.dialect == mxConstants.DIALECT_SVG)
	{
		bend.dialect = mxConstants.DIALECT_PREFERHTML;
		bend.init(this.graph.container);
	}
	else
	{
		bend.dialect = this.graph.dialect;
		bend.init(this.graph.getView().getOverlayPane());
	}
};
mxEdgeHandler.prototype.mouseDown = function(sender, me)
{
	if (me.getState() == this.state)
	{
		if (me.getHandle() == mxEvent.LABEL_HANDLE && !this.graph.isLabelMovable(me.getCell()))
		{
			me.consume();
		}
		else if (this.graph.isEnabled()
			&& !this.graph.isForceMarqueeEvent(me.getEvent()) && this.marker != null && me.getHandle() != null)
		{
			this.start(me.getX(), me.getY(), me.getHandle());
			me.consume();
		}
	}
};
mxEdgeHandler.prototype.start = function(x, y, index)
{
	this.startX = x;
	this.startY = y;
	this.isSource = index == 0;
	this.isTarget = index == this.bends.length - 1;
	this.isLabel = index == mxEvent.LABEL_HANDLE;

	if (this.isSource || this.isTarget)
	{
		var cell = this.state.cell;
		var terminal = this.graph.model.getTerminal(cell, this.isSource);

		if (terminal == null || this.graph.isCellDisconnectable(cell, terminal, this.isSource))
		{
			var p0 = this.abspoints[0];
			var pe = this.abspoints[this.abspoints.length - 1];
			this.abspoints = [];
			this.abspoints.push(p0);
			this.abspoints.push(pe);
			this.index = index;
		}
	}
	else
	{
		this.index = index;
	}
};
mxEdgeHandler.prototype.mouseMove = function(sender, me)
{
	if (this.index != null && this.marker != null)
	{
		var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
		var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY(), gridEnabled);

		if (gridEnabled)
		{
			var view = this.graph.getView();
			var scale = view.scale;
			point.x = this.graph.snap(point.x / scale) * scale;
			point.y = this.graph.snap(point.y / scale) * scale;
		}

		if (this.isLabel)
		{
			this.label.x = point.x;
			this.label.y = point.y;
		}
		else
		{

			var clone = this.state.clone();
			var geometry = this.graph.getCellGeometry(this.state.cell);
			var points = geometry.points;
			var source = null;
			var target = null;

			if (this.isSource || this.isTarget)
			{
				this.marker.process(me);
				var currentState = this.marker.getValidState();
				target = this.graph.getView().getVisibleTerminal(this.state.cell, !this.isSource);

				if (currentState != null)
				{
					source = currentState.cell;
				}
				else
				{
					clone.setAbsoluteTerminalPoint(point, this.isSource);

					if (this.marker.getMarkedState() == null)
					{
						this.error = (this.graph.allowDanglingEdges) ? null : '';
					}
				}

				if (!this.isSource)
				{
					var tmp = source;
					source = target;
					target = tmp;
				}
			}
			else
			{
				this.convertPoint(point, gridEnabled);

				if (points == null)
				{
					points = [point];
				}
				else
				{
					points[this.index - 1] = point;
				}
				this.points = points;
				this.active = true;
				source = clone.view.getVisibleTerminal(this.state.cell, true);
				target = clone.view.getVisibleTerminal(this.state.cell, false);
			}
			clone.view.updatePoints(clone, points, source, target);
			clone.view.updateTerminalPoints(clone, source, target);

			var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor;
			this.setPreviewColor(color);
			this.abspoints = clone.absolutePoints;
		}
		this.drawPreview();
		me.consume();
	}
};
mxEdgeHandler.prototype.mouseUp = function(sender, me)
{
	if (this.index != null && this.marker != null)
	{
		var edge = this.state.cell;

		if (me.getX() != this.startX || me.getY() != this.startY)
		{
			if (this.error != null)
			{
				if (this.error.length > 0)
				{
					this.graph.validationAlert(this.error);
				}
			}
			else if (this.isLabel)
			{
				this.moveLabel(this.state, this.label.x, this.label.y);
			}
			else if (this.isSource || this.isTarget)
			{
				if (this.marker.hasValidState())
				{
					var edge = this.connect(edge, this.marker.getValidState().cell, this.isSource,
						this.graph.isCloneEvent(me.getEvent()) && this.cloneEnabled
							&& this.graph.isCellsCloneable());
				}
				else if (this.graph.allowDanglingEdges)
				{
					var pt = this.graph.getPointForEvent(me.getEvent());
					var pstate = this.graph.getView().getState(this.graph.getModel().getParent(edge));

					if (pstate != null)
					{
						pt.x -= pstate.origin.x;
						pt.y -= pstate.origin.y;
					}
					this.changeTerminalPoint(edge, pt, this.isSource);
				}
			}
			else if (this.active)
			{
				this.changePoints(edge, this.points);
			}
			else
			{
				this.graph.getView().invalidate(this.state.cell);
				this.graph.getView().revalidate(this.state.cell);
			}
			this.abspoints = this.state.absolutePoints;
		}

		if (this.marker != null)
		{
			this.reset();

			if (edge != this.state.cell)
			{
				this.graph.setSelectionCell(edge);
			}
		}
		me.consume();
	}
};
mxEdgeHandler.prototype.reset = function()
{
	this.error = null;
	this.index = null;
	this.label = null;
	this.active = false;
	this.isLabel = false;
	this.isSource = false;
	this.isTarget = false;
	this.marker.reset();
	this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
	this.redraw();
};
mxEdgeHandler.prototype.setPreviewColor = function(color)
{
	if (this.shape != null && this.shape.node != null)
	{
		if (this.shape.dialect == mxConstants.DIALECT_SVG)
		{
			this.shape.innerNode.setAttribute('stroke', color);
		}
		else
		{
			this.shape.node.setAttribute('strokecolor', color);
		}
	}
};
mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
{
	var scale = this.graph.getView().getScale();
	var tr = this.graph.getView().getTranslate();

	if (gridEnabled)
	{
		point.x = this.graph.snap(point.x);
		point.y = this.graph.snap(point.y);
	}
	point.x = point.x / scale - tr.x;
	point.y = point.y / scale - tr.y;
	return point;
};
mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y)
{
	var model = this.graph.getModel();
	var geometry = model.getGeometry(edgeState.cell);

	if (geometry != null)
	{
		geometry = geometry.clone();
		var pt = this.graph.getView().getRelativePoint(edgeState, x, y);
		geometry.x = pt.x;
		geometry.y = pt.y;

		var scale = this.graph.getView().scale;
		geometry.offset = new mxPoint(0, 0);
		var pt = this.graph.view.getPoint(edgeState, geometry);
		geometry.offset = new mxPoint((x - pt.x) / scale, (y - pt.y) / scale);
		model.setGeometry(edgeState.cell, geometry);
	}
};
mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone)
{
	var model = this.graph.getModel();
	var parent = model.getParent(edge);
	model.beginUpdate();

	try
	{
		if (isClone)
		{
			var clone = edge.clone();
			model.add(parent, clone, model.getChildCount(parent));
			var other = model.getTerminal(edge, !isSource);
			model.setTerminal(clone, other, !isSource);
			edge = clone;
		}

		if (terminal == null)
		{
			var scale = this.graph.getView().scale;
			var tr = this.graph.getView().translate;
			var pstate = this.graph.getView().getState(parent);
			var dx = (pstate != null) ? pstate.origin.x : 0;
			var dy = (pstate != null) ? pstate.origin.y : 0;
			var current = this.abspoints[(isSource) ? 0 : this.abspoints.length - 1];
			var geo = model.getGeometry(edge).clone();
			geo.setTerminalPoint(new mxPoint((current.x - dx) / scale - tr.x, (current.y - dy) / scale - tr.y),
				isSource);
			model.setGeometry(edge, geo);
			model.setTerminal(edge, null, isSource);
		}
		else
		{
			this.graph.connectCell(edge, terminal, isSource);
		}
	}
	finally
	{
		model.endUpdate();
	}
	return edge;
};
mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource)
{
	var model = this.graph.getModel();
	var geo = model.getGeometry(edge);

	if (geo != null)
	{
		model.beginUpdate();

		try
		{
			geo = geo.clone();
			geo.setTerminalPoint(point, isSource);
			model.setGeometry(edge, geo);
			model.setTerminal(edge, null, isSource);
		}
		finally
		{
			model.endUpdate();
		}
	}
};
mxEdgeHandler.prototype.changePoints = function(edge, points)
{
	var model = this.graph.getModel();
	var geo = model.getGeometry(edge);

	if (geo != null)
	{
		geo = geo.clone();
		geo.points = points;
		model.setGeometry(edge, geo);
	}
};
mxEdgeHandler.prototype.getHandleFillColor = function(index)
{
	var isSource = index == 0;
	var cell = this.state.cell;
	var terminal = this.graph.getModel().getTerminal(cell, isSource);
	var color = mxConstants.HANDLE_FILLCOLOR;

	if (terminal != null)
	{
		if (this.graph.isCellDisconnectable(cell, terminal, isSource))
		{
			color = mxConstants.CONNECT_HANDLE_FILLCOLOR;
		}
		else
		{
			color = mxConstants.LOCKED_HANDLE_FILLCOLOR;
		}
	}
	return color;
}
mxEdgeHandler.prototype.redraw = function()
{
	this.abspoints = this.state.absolutePoints;
	var cell = this.state.cell;
	var s = (this.graph.dialect == mxConstants.DIALECT_SVG) ? 2 : 3;
	this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
	var bounds = new mxRectangle(this.label.x - s, this.label.y - s, 2 * s, 2 * s);
	this.labelShape.bounds = bounds;
	this.labelShape.redraw();
	var lab = this.graph.getLabel(cell);

	if (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell))
	{
		this.labelShape.node.style.visibility = 'visible';
	}
	else
	{
		this.labelShape.node.style.visibility = 'hidden';
	}

	if (this.bends != null && this.bends.length > 0)
	{
		var model = this.graph.getModel();
		s = (this.graph.dialect == mxConstants.DIALECT_SVG) ? 3 : 4;
		var n = this.abspoints.length - 1;
		var p0 = this.abspoints[0];
		var x0 = this.abspoints[0].x;
		var y0 = this.abspoints[0].y;
		this.bends[0].bounds = new mxRectangle(x0 - s, y0 - s, 2 * s, 2 * s);
		this.bends[0].fill = this.getHandleFillColor(0);
		this.bends[0].reconfigure();
		this.bends[0].redraw();
		var pe = this.abspoints[n];
		var xn = this.abspoints[n].x;
		var yn = this.abspoints[n].y;
		var bn = this.bends.length - 1;
		this.bends[bn].bounds = new mxRectangle(xn - s, yn - s, 2 * s, 2 * s);
		this.bends[bn].fill = this.getHandleFillColor(bn);
		this.bends[bn].reconfigure();
		this.bends[bn].redraw();
		this.redrawInnerBends(p0, pe);
	}
	this.drawPreview();
};
mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
{
	var s = (this.graph.dialect == mxConstants.DIALECT_SVG) ? 3 : 4;
	var g = this.graph.getModel().getGeometry(this.state.cell);
	var pts = g.points;

	if (pts != null)
	{
		for (var i = 1; i < this.bends.length - 1; i++)
		{
			if (this.abspoints[i] != null)
			{
				var x = this.abspoints[i].x;
				var y = this.abspoints[i].y;
				this.bends[i].node.style.visibility = 'visible';
				this.bends[i].bounds = new mxRectangle(x - s, y - s, 2 * s, 2 * s);
				this.bends[i].redraw();
				this.points[i - 1] = pts[i - 1];
			}
			else if (this.bends[i] != null)
			{
				this.bends[i].destroy();
				this.bends[i] = null;
			}
		}
	}
};
mxEdgeHandler.prototype.drawPreview = function()
{
	if (this.isLabel)
	{
		var s = (this.graph.dialect == mxConstants.DIALECT_SVG) ? 2 : 3;
		var bounds = new mxRectangle(this.label.x - s, this.label.y - s, 2 * s, 2 * s);
		this.labelShape.bounds = bounds;
		this.labelShape.redraw();
	}
	else
	{
		this.shape.points = this.abspoints;
		this.shape.redraw();
	}
	mxUtils.repaintGraph(this.graph, this.shape.points[this.shape.points.length - 1]);
};
mxEdgeHandler.prototype.installListeners = function(bend, handle)
{
	var self = this;
	mxEvent.addListener(bend.node, 'mousedown', function(evt)
	{
		self.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, self.state, handle));
	});
	mxEvent.addListener(bend.node, 'mouseup', function(evt)
	{
		self.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, self.state, handle));
	});
};
mxEdgeHandler.prototype.destroy = function()
{
	this.graph.removeMouseListener(this);
	this.marker.destroy();
	this.marker = null;
	this.shape.destroy();
	this.shape = null;
	this.labelShape.destroy();
	this.labelShape = null;

	if (this.bends != null)
	{
		for (var i = 0; i < this.bends.length; i++)
		{
			if (this.bends[i] != null)
			{
				this.bends[i].destroy();
				this.bends[i] = null;
			}
		}
	}
};